﻿using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using TYBase;
using TYPLC.PLC;
using TYRTUBase;

namespace TYPLC.Omron
{
    public class OmronPLC : IPLCBase<OmronAddressData>
    {
        ITYClient rtu;
        //这是一个串口,他需要串口参数
        public OmronPLC(ITYClient rtu)
        {
            this.rtu = rtu;
        }

        public override bool Connect()
        {
            if (rtu.Connect() == false)
            {
                return false;
            }
            if (dicAddress.Count > 0)
            {
                var address = dicAddress.FirstOrDefault().Key;
                //if (AddressAs(value, out OmronAddressData data) == false)
                //{
                //    return false;
                //}
                return Read(address, out object val);
            }
            return true;
           
        }

        public override bool AddressAs(string address, out OmronAddressData data)
        {
            data = null;
            int typeLen = -1;
            for (int i = 0; i < address.Length; i++)
            {
                if (address[i] >= '0' && address[i] <= '9')
                {
                    typeLen = i;
                    break;
                }
            }

            if (typeLen == -1)
                return false;
            data = new OmronAddressData();
            var strType = address.Left(typeLen).ToLower();
            var strAddressWithOutType = address.Right(address.Length - typeLen);

            var indexStart1 = strAddressWithOutType.IndexOf("[");
            var indexStart2 = strAddressWithOutType.IndexOf("]");
            if (indexStart1 == -1 && indexStart2 == -1)
            {
                data.IsList = false;
                data.ReadLength = 1;

            }
            else
            {
                if (indexStart1 == -1 || indexStart2 == -1)
                {
                    return false;
                }
                data.ReadLength = strAddressWithOutType.Substring(indexStart1 + 1, indexStart2 - indexStart1 - 1).Asushort();
            }
            if (indexStart1 > 0)
            {
                strAddressWithOutType = strAddressWithOutType.Substring(0, indexStart1);
            }
            var indexDot = strAddressWithOutType.IndexOf(".");
            var tmps = strAddressWithOutType.Split(".");
            data.Chan = tmps[0].Asushort();
            if (tmps.Length >= 2)
            {
                data.StartBit = tmps[1].AsByte();
                data.DataType = OmronDataType.Bit;
            }
            else
            {
                data.StartBit = 0;
                data.DataType = OmronDataType.DWord;
            }
            if (data.Chan < 0)
            {
                return false;
            }
            if (data.ReadLength <= 0)
            {
                return false;
            }
            return true;
        }

        public override bool ReadPri(OmronAddressData address, out object value)
        {
            try
            {
                switch (address.DataType)
                {
                    case OmronDataType.Bit:
                        return ReadBits(address, out value);
                    case OmronDataType.DWord:
                        return ReadDWord(address, out value);
                    default:
                        break;
                }
            }
            catch (Exception ex)
            {
                Log.E($"ReadPri {ex.ExceptionInfo()}");
            }
            value = null;
            return false;
        }

        public override bool WritePri(OmronAddressData address, object value)
        {
            switch (address.DataType)
            {
                case OmronDataType.Bit:
                    {
                        if (value is bool bool_val)
                        {
                            Log.I($"WriteBits value bool:{bool_val}");
                            return WriteBits(address, bool_val);
                        }
                        else if (value is int int_val)
                        {
                            Log.I($"WriteBits value int:{int_val}");
                            return WriteBits(address, (int_val == 1));
                        }
                        else if (value is bool[] bools_val)
                        {
                            return WriteBits(address, bools_val);
                        }
                        else if (value is int[] ints_val)
                        {
                            bool[] bools_val2 = new bool[ints_val.Length];
                            for (int i = 0; i < ints_val.Length; i++)
                            {
                                bools_val2[i] = (ints_val[i] == 1);
                            }
                            return WriteBits(address, bools_val2);
                        }
                    }
                    break;
                case OmronDataType.DWord:
                    {
                        if (value is int int_val)
                        {
                            return WriteD(address, int_val);
                        }
                        else if (value is int[] ints_val)
                        {
                            return WriteD(address, ints_val);
                        }
                    }
                    break;

                default:
                    break;
            }
            return false;
        }

        private object objLock = new object();
        private bool SendAndWait(string sendStr, out string strReceive)
        {
            byte[] byteAll = null;
            lock (objLock)
            {
                rtu.ClearInBuff();
                rtu.Write(sendStr.AsBytes());
                Stopwatch sw = Stopwatch.StartNew();
                while (sw.ElapsedMilliseconds < 5 * 1000)
                {
                    Thread.Sleep(1);
                    if (rtu.Read(out var receData) > 0)
                    {
                        byteAll = byteAll.Append(receData);
                        var listbytes = byteAll.Split(NewLine);
                        if (listbytes != null && listbytes.Count > 0)
                        {
                            var lastData = listbytes[listbytes.Count - 1];
                            var index = lastData.Indexof(NewLine);
                            if (index > 0)
                            {
                                byte[] dataget = new byte[index];
                                Array.Copy(lastData, dataget, index);
                                strReceive = dataget.AsString();
                                if (CheckFCS(strReceive))
                                {
                                    return true;
                                }
                            }
                        }
                    }
                }
            }
            Log.I("omron outtime");
            Log.I("omron send:" + sendStr);
            Log.I("omron rece:" + byteAll.AsString());
            strReceive = null;
            return false;
        }

        internal byte[] NewLine = new byte[] { 0x0D };
        private bool Send(string sendStr)
        {

            byte[] byteAll = null;
            lock (objLock)
            {
                rtu.ClearInBuff();
                rtu.Write(sendStr.AsBytes());
                Stopwatch sw = Stopwatch.StartNew();

                var strReceive = "";
                while (sw.ElapsedMilliseconds < 5 * 1000)
                {
                    Thread.Sleep(1);
                    if (rtu.Read(out var receData) > 0)
                    {
                        byteAll = byteAll.Append(receData);
                        var listbytes = byteAll.Split(NewLine);
                        if (listbytes != null && listbytes.Count > 0)
                        {
                            var lastData = listbytes[listbytes.Count - 1];
                            var index = lastData.Indexof(NewLine);
                            if (index > 0)
                            {
                                byte[] dataget = new byte[index];
                                Array.Copy(lastData, dataget, index);
                                strReceive = dataget.AsString();
                                if (CheckFCS(strReceive))
                                {
                                    return true;
                                }
                            }
                        }

                    }
                }
            }
            Log.I("omron outtime");
            Log.I("omron send:" + sendStr);
            Log.I("omron rece:" + byteAll.AsString());
            return false;
        }


        private bool ReadBits(OmronAddressData address, out object value)
        {
            string strChan = $"{address.Chan:X4}";
            string strStartBit = $"{address.StartBit:X2}";
            string strLength = $"{address.ReadLength:X4}";
            string strFrame = "@00FA000000000010130" + strChan + strStartBit + strLength;
            var text = ComputeFCS(strFrame);
            var strSend = strFrame + text + "*" + '\r';
            if (!SendAndWait(strSend, out string strReceive))
            {
                value = null;
                return false;
            }
            var strData = strReceive.Substring(23, address.ReadLength * 2);

            if (address.ReadLength == 1)
            {
                value = strData == "01";
                return true;
            }
            bool[] results = new bool[address.ReadLength];
            for (int i = 0; i + 1 < address.ReadLength; i += 2)
            {
                results[i] = strData[i + 1] == '1';
            }
            value = results;
            return true;
        }

        private bool WriteBits(OmronAddressData address, bool val)
        {
            string text = $"{address.Chan:X4}";
            string text2 = $"{address.StartBit:X2}";
            string text3 = $"{address.ReadLength:X4}";
            //1200.1
            string text4 = "@00FA000000000010230" + text + text2 + text3 + (val ? "01" : "00");
            //text:@00FA000000000010230 04B0 01 0001 01 00*
            string text5 = ComputeFCS(text4);
            text4 = text4 + text5 + "*" + '\r';
            Log.I($"WriteBit send:{val} text:" + text4);
            return Send(text4);
        }

        private bool WriteBits(OmronAddressData address, bool[] val)
        {
            string text = $"{address.Chan:X4}";
            string text2 = $"{address.StartBit:X2}";
            string text3 = $"{address.ReadLength:X4}";
            string bools = "";
            foreach (var item in val)
            {
                bools += (item ? "01" : "00");
            }
            string text4 = "@00FA000000000010230" + text + text2 + text3 + bools;
            string text5 = ComputeFCS(text4);
            text4 = text4 + text5 + "*" + '\r';
            Log.I("WriteBits send:" + text4);
            return Send(text4);
        }

        private bool ReadDWord(OmronAddressData address, out object data)
        {
            string readCountstr = $"{address.Chan:X4}";
            string FCSstr = $"{address.ReadLength:X4}";
            string outstring = "@00FA000000000010182" + readCountstr + "00" + FCSstr;
            string text = ComputeFCS(outstring);
            outstring = outstring + text + "*" + '\r';

            if (!SendAndWait(outstring, out string strReceive))
            {
                data = null;
                return false;
            }
            if (address.ReadLength == 1)
            {
                data = Convert.ToUInt16(strReceive.Substring(23, 4), 16);
                return true;
            }

            var ushort_data = new ushort[address.ReadLength];
            for (int i = 0; i < address.ReadLength; i++)
            {
                ushort_data[i] = Convert.ToUInt16(strReceive.Substring(23 + i * 4, 4), 16);
            }
            data = ushort_data;

            return true;
        }

        public bool WriteD(OmronAddressData address, int[] Content)
        {
            string text = string.Empty;
            foreach (int num in Content)
            {
                text += $"{num:X4}";
            }

            string text2 = $"{address.Chan:X4}";
            string text3 = $"{address.ReadLength:X4}";
            string text4 = "@00FA000000000010282" + text2 + "00" + text3 + text;
            string text5 = ComputeFCS(text4);
            text4 = text4 + text5 + "*" + '\r';
            return Send(text4);
        }

        public bool WriteD(OmronAddressData address, int val)
        {
            string text = string.Empty;
            text += $"{val:X4}";
            string text2 = $"{address.Chan:X4}";
            string text3 = $"{address.ReadLength:X4}";
            string text4 = "@00FA000000000010282" + text2 + "00" + text3 + text;
            string text5 = ComputeFCS(text4);
            text4 = text4 + text5 + "*" + '\r';
            return Send(text4);
        }

        private static string ComputeFCS(string linkstring)
        {
            char outstring = linkstring[0];
            int receivestr = outstring;
            for (int chNumstr = 1; chNumstr < linkstring.Length; chNumstr++)
            {
                outstring = linkstring[chNumstr];
                receivestr ^= outstring;
            }
            return receivestr.ToString("X2");
        }

        public static bool CheckFCS(string receives)
        {
            int outstring = receives.IndexOf('*');
            string receivestr = receives.Substring(0, outstring - 2);
            if (receives.Substring(outstring - 2, 2).Equals(ComputeFCS(receivestr)))
            {
                return true;
            }
            return false;
        }
    }

    public class OmronAddressData
    {
        public ushort Chan { get; set; }
        public byte StartBit { get; set; }
        public ushort ReadLength { get; set; }
        public OmronDataType DataType { get; set; }
        public bool IsList { get; set; }
        public EnumOmornPlcMemory PlcMemory { get; set; }
    }

    public enum OmronDataType
    {
        Bit,
        DWord,
    }



}
